﻿package com.dg.gaming.api.common
{
	import com.dg.gaming.api.common.msg.*;
	import com.dg.gaming.api.common.playerstate.*;
	import com.ericfeminella.collections.ArrayList;
	import com.dg.gaming.api.common.util.*;
	import nanoxml.XMLElement;

	/**
	 * ...
	 * @author
	 */
	public class Table extends Observable implements ITransmittable
	{


		private static const XML_ATT_TABLE_NUM:String="tableNum";
		private static const XML_ATT_VIS:String="vis";
		private static const XML_ATT_TIME_STARTED:String="timeStarted";

		// Properties
		public static const NUM_OF_PLAYERS:String="numOfPlayers";
		private var fullTransmittable:Boolean=false;
		// Declare fields
		private var tableNumber:String;
		private var _isPublic:Boolean; // tables can be public / private
		private var startTime:Date; // time started
		private var playerList:PlayerList; // list of players
		// e.g. required number of people
		//      for this game
		private var inviteList:ArrayList; // list of invites active on this table
		private var drawOfferActive:Boolean; // if a draw offer is active
		private var drawOfferSerialNum:int; // serial number of the current draw offer
		private var drawOfferResponseArray:Array; // Array of draw offer responses

		// A game can contain a model and this state is stored on the table.
		private var model:GameModel; // only used if DGServer stores the state of a game

		private var game:Game=null;

		public function Table()
		{
			playerList=new PlayerList;
		}

		/**
		 * Create a table and 1 user (owner)
		 *
		 * @param tableNumber  The table number of the new table.
		 * @param theUser      The user creating the table (the owner).
		 * @param isPublic     boolean to show if a table is public or private.
		 */
		public function init(tableNumber:String, theUser:User, isPublic:Boolean):void
		{
			this.tableNumber=tableNumber;
			this._isPublic=isPublic;
			this.startTime=new Date;
			this.inviteList=new ArrayList;
			this.drawOfferActive=false;
			this.drawOfferSerialNum=0;

			// Set game data to null
			model=null;


			playerList.init(theUser.userid);
			playerList.addPlayer(theUser);
		}

		/**
		 * Create Table object from a String.
		 *
		 * @param message                 XMLElement communication object.
		 * @throws TransmissionException  Thrown if there is a problem in transmission.
		 * @throws NumberFormatException  Problem parsing an int.
		 */ /* RAW: Maybe make this take a userList as a parameter and to the
		   playerList.UpdateUsers() automatically?   Maybe do this
		 for setPlayerList as well? */
		public function initXML(message:XMLElement):void
		{
			this.tableNumber=message.getStringAttribute(XML_ATT_TABLE_NUM);
			this._isPublic=message.getAttribute(XML_ATT_VIS) == "pub";

			var dateStr:String=message.getStringAttribute(XML_ATT_TIME_STARTED);
			this.startTime=DGUtils.readDate(dateStr, IGame.DATE_FORMAT_FULL);

			// Parse the player list and the optional model
			var e:Array=message.chileds;
			for (var i:int=0; i < e.length; i++)
			{
				var childMessage:XMLElement=XMLElement(e[i]);
				if (childMessage.name == Msg.PLAYER_LIST)
				{
					playerList=new PlayerList;
					playerList.initXml(childMessage);
				}
				else
				{
					if (childMessage.name == Msg.MODEL)
					{
						if (model != null)
						{
							model.setState(childMessage);
						}
					}
					else
					{
						if (childMessage.name == Msg.GAME)
						{
							var game:Game=new Game;
							game.initXML(childMessage);
							setGame(game);
						}
					}
				}
			}
		}

		/**
		 * Add a property to the hash map.
		 *
		 * @param key        Key of the value to add.
		 * @param value      Value of the property
		 */ /*
		   public function addProperty(key:String, value:String):void {

		   // Update properties
		   properties.put(key, value);

		   // notify any listeners on data
		   setChanged();
		   notifyObservers("+p" + key + " " + value);
		 }*/

		/**
		 * Return a property from a key.  If key does not exist, then
		 * return default value;
		 *
		 * @param key
		 * @return
		 */ /*
		   public function getProperty(key:String, defaultValue:String):String {
		   var property:String= getProperty(key);
		   if (property == null) {
		   return defaultValue;
		   }
		   return property;
		 }*/

		/**
		 * Return a property from a key.
		 *
		 * @param key
		 * @return
		 */ /*
		   public function getProperty(key:String):String {

		   // Update properties
		   return (String) properties.get(key);
		   }
		 */ /**
		 * Add a user to the table i.e. a user joins a table
		 *
		 * @param theUser   The user to add to the table.
		 */
		public function addPlayer(theUser:User):Boolean
		{
			return playerList.addPlayer(theUser);
		}

		/**
		 * Game over.  Update all players that are already seated
		 * to be ready for press start again.
		 */
		public function gameOver():void
		{
			// Update all the users to be seated and ready to press start
			var players:Array=getPlayerList().getPlayers();
			for (var i:int=0; i < players.length; i++)
			{
				var player:Player=Player(players[i]);
				if (player.seatNum != Player.NOT_SEATED)
				{
					player.playerState=new PlayerStateSeated;
				}
			}
			getPlayerList().refreshObservers();
		}

		/**
		 * Get the number of this table.
		 *
		 * @return  table number as an integer.
		 */
		public function getTableNum():String
		{
			return tableNumber;
		}

		/**
		 * Return the time this table was created.
		 *
		 * @return   Time table was created.
		 */
		public function getStartTime():Date
		{
			return this.startTime;
		}

		/**
		 * Return the number of players as specified by a client
		 * through a property.
		 *
		 * @return  Minimum number of players
		 */
		public function getNumOfPlayers():int
		{
			/*var numPlayersProp:String= String(properties.get(Msg.PROP_PLAYERS));
			   if (numPlayersProp != null) {
			   return Integer.parseInt(numPlayersProp);
			 }*/

			return IGame.DEFAULT_NUM_OF_PLAYERS; // 2
		}

		/**
		 * Return a player list.
		 *
		 * @return    PlayerList at this table.
		 */
		public function getPlayerList():PlayerList
		{
			return playerList;
		}

		public function setPlayerList(playerList:PlayerList):void
		{
			this.playerList=playerList;
		}

		/**
		 * Returns true if a specified user is at a table.
		 *
		 * @param user  Username to check.
		 * @return      True if specified is at table.
		 */
		public function containsPlayer(user:Number):Boolean
		{
			return (playerList.containsUser(user));
		}

		/**
		 * Returns true if a specified user is at a table and is seated in a seat.
		 * ie: Is <i>not</i> just a spectator.
		 *
		 * @param user  Username to check.
		 * @return      True if user is at the table and not a spectator.
		 */
		public function isParticipatingPlayer(userID:Number):Boolean
		{
			var thePlayer:Player=playerList.getPlayer(userID);
			return thePlayer != null && thePlayer.seatNum != Player.NOT_SEATED;
		}

		/**
		 * Get the owner of this table.
		 *
		 * @return  Username of owner.
		 */
		public function getOwner():Number
		{
			return playerList.getOwner();
		}

		/**
		 * Returns true if this is a public table.
		 *
		 * @return  True if table is public.
		 */
		public function isPublic():Boolean
		{
			return _isPublic;
		}

		/**
		 * Retrieve the model from the server (if its being used).
		 *
		 * @return  Model (will be null if doesn't exist).
		 */
		public function getModel():GameModel
		{
			return model;
		}

		/**
		 * Set the game data.
		 *
		 * @param model
		 */
		public function setModel(model:GameModel):void
		{
			this.model=model;
		}

		public function setGame(game:Game):void
		{
			this.game=game;
		}

		public function setTableNumber(tableNumber:String):void
		{
			this.tableNumber=tableNumber;
		}

		public function setFullTransmittable(fullTransmittable:Boolean):void
		{
			this.fullTransmittable=fullTransmittable;
		}

		/**
		 * Sets the next player.
		 */
		public function nextPlayerById(playerId:Number):void
		{
			playerList.nextPlayerById(playerId); // next player
		}

		/**
		 * Sets the next player.
		 */
		public function nextPlayerPlayer(player:Player):void
		{
			playerList.nextPlayerByPlayer(player); // next player
		}

		/**
		 * Sets the next player.
		 */
		public function nextPlayerSeatNum(seatNum:int):void
		{
			playerList.nextPlayerSeatNum(seatNum); // next player
		}

		/**
		 * Sets the next player username.
		 */
		public function nextPlayer():void
		{
			playerList.nextPlayer(); // next player
		}


		/**
		 * Flatten the table object.  Also include the model of the current game
		 * if the modelStateIsAttached variable is set.
		 *
		 * @return                     XML element version of object.
		 */
		public function flatten():XMLElement
		{
			// Format time started into a String
			var timeStartedTrans:String=DGUtils.valueOf(startTime, IGame.DATE_FORMAT_FULL);

			// Create <table> element and populate with data for transmission
			var message:XMLElement=new XMLElement;
			message.name=Msg.TABLE;
			message.setAttribute(XML_ATT_TABLE_NUM, tableNumber);
			var visStr:String=_isPublic ? "pub" : "priv"; // TODO make better at some stage
			message.setAttribute(XML_ATT_VIS, visStr);
			message.setAttribute(XML_ATT_TIME_STARTED, timeStartedTrans);
			//message.setAttribute(XML_ATT_PROPERTIES, properties);
			var fullTransmittable:Boolean=isFullTransmittable();
			// Add player list as a child element
			var playerListElement:XMLElement=fullTransmittable ? playerList.flattenWithUser() : playerList.flatten();
			message.addChild(playerListElement);
			if (fullTransmittable)
			{
				if (model != null)
				{
					message.addChild(model.flatten());
				}
				if (game != null)
				{
					message.addChild(game.flatten());
				}
			}
			setFullTransmittable(false);
			return message;
		}

		/**
		 * Return the time started as a String.
		 *
		 * @return
		 */
		public function getTimeFormatted():String
		{

			return startTime.toDateString();
		}

		/**
		 * Return the String version of this table.
		 *
		 * @see java.lang.Object#toString()
		 */
		public override function toString():String
		{
			var isPublicStr:String=_isPublic ? "public" : "private";
			var sb:StringBuffer=new StringBuffer(tableNumber + ": (" + DGUtils.valueOf(startTime, IGame.DATE_FORMAT_TIME) + " - " + isPublicStr + " table) - ");

			// Loop through the players and create the correct
			var players:Array=playerList.getPlayersSortedBySeatPlayer();
			for (var i:int=0; i < players.length; i++)
			{

				// Retreive player
				var player:Player=Player(players[i]);

				// Append the players name
				sb.append(player.playerId);

				// Check if owner
				if (player.playerId == playerList.getOwner())
				{
					if (i != players.size())
					{
						sb.append(" (" + DGLabels.getInstance().getValue("owner") + ")");
					}
				}

				if (i < players.size() - 1)
				{
					sb.append(", "); // seperate with comma.
				}
			}

			return sb.toString();
		}

		/**
		 * Return true if a game is in progress.  This method loops through the
		 * various Player object in the PlayerList to see if any of them are in the
		 * PlayerStateGameStarted state.
		 *
		 * @return  True if game is playing.
		 */
		public function isGamePlaying():Boolean
		{
			var players:Array=getPlayerList().getPlayers();
			for (var i:int=0; i < players.length; i++)
			{
				var player:Player=Player(players[i]);
				// is the state of tihs player equal to "game started"?
				if (player.getState().stringValue() == PlayerState.GAME_STARTED)
				{
					return true;
				}
			}

			return false;
		}

		/**
		 * Return the properties hash.
		 *
		 * @see com.dg.gaming.api.common.msg.ITransmittableWithProps#getProperties()
		 */

		public function getGame():Game
		{
			return game;
		}

		public function isFullTransmittable():Boolean
		{
			return fullTransmittable;
		}

		/**
		 * Add a player to the invite list of this table
		 *
		 * @param playerName  The player name to be added to the invite list
		 */
		public function addInvite(playerName:Number):void
		{
			// Make sure that this player isn't already invited to the table
			if (inviteList.indexOf(playerName) < 0)
			{
				//inviteList.add(playerName);
			}
		}

		/**
		 * Determine if the given player is invited to this table
		 *
		 * @param playerName   The player to check for invitation status
		 *
		 * @return true if the player is invited to the table.
		 */
		public function isInvited(playerName:String):Boolean
		{
			return inviteList.indexOf(playerName) >= 0;
		}

		/**
		 * Remove an invite from the table
		 *
		 * @param playerName   The player to remove from the invite list
		 *
		 * @return true if the player had been invited to the table.
		 */
		public function removeInvite(playerName:Number):Boolean
		{
			return false; //inviteList.remove(playerName);
		}

		/**
		 * Check to see if there is an outstanding draw offer or not.
		 */
		public function hasOutstandingDrawOffer():Boolean
		{
			return drawOfferActive;
		}

		/**
		 * Start a new draw offer with the given player as the initiator.
		 *
		 * Note: This assumes that thePlayer is an active player of the game (he
		 *       is <i>not<i/> a spectator).  This check is done in
		 *       ServerTableController, but if someone changes the code, make sure
		 *       that this is only called for known active players.
		 *
		 * @param thePlayer   The player asking for the draw offer.
		 * @return the serial number of the draw offer.
		 */
		public function startDrawOffer(thePlayer:Player):int
		{
			// Can't start a new offer if there is an old one still active
			if (drawOfferActive)
			{
				return -1;
			}

			// Advance to the next serial number
			drawOfferSerialNum+=1;

			// Initialize the response array to all false;
			drawOfferResponseArray=new Array[getNumOfPlayers()];

			// The initiating player implicitly accepts his own offer.
			drawOfferResponseArray[thePlayer.seatNum]=true;

			return drawOfferSerialNum;
		}

		/**
		 * Accept a draw offer for a player.
		 *
		 * Note: This assumes that thePlayer is an active player of the game (he
		 *       is <i>not<i/> a spectator).  This check is done in
		 *       ServerTableController, but if someone changes the code, make sure
		 *       that this is only called for known active players.
		 *
		 * @param thePlayerName   The player accepting the draw offer.
		 * @param serialNum       The serial number of the offer being accepted.
		 */
		public function acceptDrawOffer(thePlayer:Player, serialNum:int):void
		{
			// Make sure that this offer is not stale before accepting it.
			if (serialNum == drawOfferSerialNum)
			{
				drawOfferResponseArray[thePlayer.seatNum]=true;
			}
		}

		/**
		 * Decline a draw offer for a player.
		 *
		 * Note: This assumes that thePlayer is an active player of the game (he
		 *       is <i>not<i/> a spectator).  This check is done in
		 *       ServerTableController, but if someone changes the code, make sure
		 *       that this is only called for known active players.
		 *
		 * @param thePlayerName   The player accepting the draw offer.
		 * @param serialNum       The serial number of the offer being accepted.
		 * @return true if the draw offer declination is accepted.
		 */
		public function declineDrawOffer(thePlayer:Player, serialNum:int):Boolean
		{
			// Make sure that this offer is not stale before accepting it.
			return serialNum == drawOfferSerialNum;
		}

		/**
		 * Determine if the current draw offer is complete (ie: all players have
		 *  accepted the draw).
		 *
		 * @return true if the current offer is complete.
		 */
		public function drawOfferComplete():Boolean
		{
			// If there is at least one player who hasn't responded yet, then the
			//   offer is not complete.
			for (var i:int=0; i < drawOfferResponseArray.length; i++)
			{
				if (drawOfferResponseArray[i] == false)
				{
					return false;
				}
			}

			// Everyone has responded, so this offer is complete.
			return true;
		}

		/**
		 * Clear the active draw offer.
		 */
		public function clearDrawOffer():void
		{
			drawOfferActive=false;
		}

		/**
		 * Refresh observers - calls the setChanged() and notifyObservers ()
		 * methods in the Observable class.
		 */
		public function refreshObservers():void
		{
			setChanged();
			notifyObservers(); // notify any class which observe this class
		}
	}


}